package com.rainbow.support.action.log.aspect;

import com.alibaba.fastjson.JSON;
import com.google.gson.GsonBuilder;
import com.nimbusds.jose.JWSObject;
import com.rainbow.common.core.constant.Constant;
import com.rainbow.common.core.toolkit.ContextHolder;
import com.rainbow.support.action.log.annotation.ActionLog;
import com.rainbow.support.action.log.handler.AbstractLogHandler;
import com.rainbow.support.action.log.handler.SysActionLog;
import com.rainbow.support.action.log.properties.ActionLogProperties;
import com.rainbow.support.action.log.util.InstantJson;
import com.rainbow.support.common.enums.SysLogEnum;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Objects;

/**
 * 日志打印切面处理
 * @author 傅为地
 */
@Aspect
@Slf4j
public class ActionLogAspect {

    /**
     * 请求参数
     */
    private String requestParams = "";

    /**
     *属性配置
     */
    private ActionLogProperties actionLogProperties;

    /**
     * 日志处理器
     */
    private AbstractLogHandler logHandler;

    private static final String IGNORED_TRACE= "Ignored_Trace";

    private static final String UNCONFIG_TRACE= "N/A";

    public ActionLogAspect(ActionLogProperties actionLogProperties, AbstractLogHandler logHandler) {
        this.actionLogProperties = actionLogProperties;
        this.logHandler = logHandler;
    }


    /**
     * 日志打印 方法执行(前/后)，开启skuwalking日志追踪
     */
    @Trace
    @Around("execution(public * *(..)) && @annotation(com.rainbow.support.action.log.annotation.ActionLog)")
    @SneakyThrows
    public Object doAroundActionLogAspectPrint(ProceedingJoinPoint pjp) {
        Method method = ((org.aspectj.lang.reflect.MethodSignature) pjp.getSignature()).getMethod();
        ActionLog actionLog = AnnotationUtils.getAnnotation(method, ActionLog.class);
        //未开启注解日志功能直接放行
        if (ObjectUtils.isEmpty(actionLog) || !actionLogProperties.isEnable()) {
            return pjp.proceed();
        }
        //局部不开启日志，直接放行
        if(ObjectUtils.isNotEmpty(actionLog.enable())&&!actionLog.enable()){
            return pjp.proceed();
        }
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(ObjectUtils.isEmpty(attributes)){
            log.warn("日志组件获取线程上下文为空!");
            return pjp.proceed();
        }
        HttpServletRequest request = attributes.getRequest();
        requestParams=getRequestParams(pjp,request);
        Object result;
        //是否异常堆栈显示
        Boolean showStack=actionLogProperties.isShowStack()&&actionLog.showStack();
        //是否显示返回值
        Boolean showResult=actionLogProperties.isShowResult()&&actionLog.showResult();
        SysActionLog logData = SysActionLog.builder().id(System.currentTimeMillis()).token(getRequestToken(request))
                .userName(getRequestUser(request))
                .trace(getRequestTrace(request))
                .project(actionLogProperties.getProject()).version(actionLogProperties.getVersion())
                .profile(actionLogProperties.getProfile()).requestUri(request.getRequestURI())
                .requestMethod(request.getMethod()).className(pjp.getTarget().getClass().getName())
                .methodName(method.getName()).actionThread(Thread.currentThread().getName())
                .requestParams(requestParams).actionStartTime(Instant.now())
                .createTime(Instant.now()).showStack(showStack).showResult(showResult).build();
        //开启注解
        logData.setMoudle(actionLog.moudle());
        //操作类型
        logData.setActionType(actionLog.actionType());
        try {
            //GET参数时，URLDecoder解析
            requestParams = "GET".equals(logData.getRequestMethod())&& StringUtils.isNotEmpty(requestParams) ? URLDecoder.decode(requestParams, StandardCharsets.UTF_8) : requestParams;
            logData.setRequestParams(requestParams);
            result = pjp.proceed();
            //结束时间
            logData.setActionEndTime(Instant.now());
            //日志记录文件
            if (!ObjectUtils.isEmpty(result)) {
                String responseParams = result instanceof String ? Objects.toString(result): new GsonBuilder().serializeNulls().registerTypeAdapter(Instant.class, new InstantJson()).create().toJson(result);
                logData.setResponseParams(responseParams);
            }
            logData.setType(SysLogEnum.TypeEnum.NORMAL.getCode());
            logData.setActionTime(logData.getActionEndTime().toEpochMilli()-logData.getActionStartTime().toEpochMilli());
            logHandler.handle(logData);
            return result;
        } catch (Exception error) {
            //结束时间
            logData.setActionEndTime(Instant.now());
            //存储异常堆栈信息到数据库,获取具
            // 体堆栈异常日志
            StringBuffer errStack = new StringBuffer(2048);
            errStack.append(error.toString());
            StackTraceElement[] stackArr = error.getStackTrace();
            if (!ObjectUtils.isEmpty(stackArr)) {
                for (StackTraceElement stack : stackArr) {
                    errStack.append("\n\tat ").append(stack);
                }
            }
            logData.setType(SysLogEnum.TypeEnum.ABNORMAL.getCode());
            logData.setException(errStack.toString());
            logData.setActionTime(logData.getActionEndTime().toEpochMilli()-logData.getActionStartTime().toEpochMilli());
            logHandler.handle(logData);
            throw error;
        }
    }
    /**
     * 参数列表，过滤不能使用JSON序列化的参数
     * @return 返回本次入参
     */
    private String getRequestParams(ProceedingJoinPoint pjp,HttpServletRequest request ){
        Object[] args = pjp.getArgs();
        Object[] params = new Object[args.length];
        if (RequestMethod.GET.name().equals(request.getMethod())) {
            requestParams = request.getQueryString();
        } else {
            //阿里的JSON针对文件格式，存在问题，这里使用谷歌的工具,
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse) {
                    continue;
                }
                params[i] = args[i];
            }
            //参数打印时处理,一个参数时不打印数组，参数为空时打印空字符串
            if(ObjectUtils.isEmpty(params)){
                requestParams="";
            }else if(ObjectUtils.isNotEmpty(params)&&params.length==1){
                requestParams=new GsonBuilder().serializeNulls().create().toJson(params[0]);
            }else{
                requestParams=new GsonBuilder().serializeNulls().create().toJson(params);
            }
        }
        return requestParams;
    }

    /**
     * 获取本次请求token，用户请求微服务的token或用户名
     * @param request 请求体
     * @return 解析请求token
     */
    private String getRequestToken(HttpServletRequest request){
        String token = "";
        if (StringUtils.isNotEmpty(request.getHeader(actionLogProperties.getToken()))) {
            token = request.getHeader(actionLogProperties.getToken());
        } else if (StringUtils.isNotEmpty(request.getParameter(actionLogProperties.getToken()))) {
            token = request.getParameter(actionLogProperties.getToken());
        } else if (org.apache.commons.lang3.ObjectUtils.isNotEmpty(request.getAttribute(actionLogProperties.getToken()))) {
            token = request.getAttribute(actionLogProperties.getToken()).toString();
        }
        return token;
    }

    /**
     * 获取请求用户名
     * 兼容jwt和普通token
     * @param request 请求体
     * @return 请求用户信息
     */
    private String getRequestUser(HttpServletRequest request) {
        String token = getRequestToken(request);
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        try {
            JWSObject jwsObject = JWSObject.parse(token);
            String userStr = jwsObject.getPayload().toString();
            return StringUtils.isEmpty(userStr)?Constant.SYSTEM_DEFAULT_USER_NAME:MapUtils.getString(JSON.parseObject(userStr),Constant.CURRENT_LOGIN_USER_NAME);
        } catch (Exception e) {
            return Objects.requireNonNullElse(ContextHolder.getUserName(), Constant.SYSTEM_DEFAULT_USER_NAME);
        }
    }

    /**
     * 微服务全局请求链路编号，
     * 1.可以采用自定义网关生成的traceId。
     * 2.也可以使用skywaliking自带的全局链路traceId
     * 3.本处优先使用本处采用skywalking自带的traceId，记录请求链路编号
     * 4.本处采用数据格式处理结果
     *  trace = StringUtils.isNotEmpty(TraceContext.traceId()) ? TraceContext.traceId() : request.getHeader(actionLogProperties.getTrace());
     *  trace = StringUtils.isNotEmpty(trace) ? trace : "";
     * @return 返回请求traceId
     */
    private String getRequestTrace(HttpServletRequest request) {
        String trace = TraceContext.traceId();
        if (StringUtils.isEmpty(trace)||Objects.equals(UNCONFIG_TRACE, trace)||Objects.equals(IGNORED_TRACE, trace)) {
            trace = request.getHeader(actionLogProperties.getTrace());
        }
        return trace;
    }
}
